package com.zqweb.gulimall.ware.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.rabbitmq.client.Channel;
import com.zqweb.common.to.SkuStockTo;
import com.zqweb.common.to.StockLockDetailTo;
import com.zqweb.common.to.StockLockTo;
import com.zqweb.common.utils.R;
import com.zqweb.common.exception.SkuWareException;
import com.zqweb.gulimall.ware.entity.WareOrderTaskDetailEntity;
import com.zqweb.gulimall.ware.entity.WareOrderTaskEntity;
import com.zqweb.gulimall.ware.feign.OrderFeignService;
import com.zqweb.gulimall.ware.feign.ProductFeignService;
import com.zqweb.gulimall.ware.service.WareOrderTaskDetailService;
import com.zqweb.gulimall.ware.service.WareOrderTaskService;
import com.zqweb.gulimall.ware.to.OrderTo;
import com.zqweb.gulimall.ware.vo.OrderItemVo;
import com.zqweb.gulimall.ware.vo.OrderVo;
import com.zqweb.gulimall.ware.vo.WareLockVo;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zqweb.common.utils.PageUtils;
import com.zqweb.common.utils.Query;

import com.zqweb.gulimall.ware.dao.WareSkuDao;
import com.zqweb.gulimall.ware.entity.WareSkuEntity;
import com.zqweb.gulimall.ware.service.WareSkuService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("wareSkuService")
@Slf4j
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private WareSkuDao wareSkuDao;
    @Autowired
    private ProductFeignService productFeignService;
    @Autowired
    private OrderFeignService orderFeignService;
    @Autowired
    private WareOrderTaskService wareOrderTaskService;
    @Autowired
    private WareOrderTaskDetailService wareOrderTaskDetailService;


    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        QueryWrapper<WareSkuEntity> queryWrapper = new QueryWrapper<>();
        String wareId = (String) params.get("wareId");
        String skuId = (String) params.get("skuId");
        if(!StringUtils.isEmpty(wareId)){
            queryWrapper.eq("ware_id",wareId);// 只有1个查询条件的写法
        }
        if(!StringUtils.isEmpty(skuId)){
            queryWrapper.eq("sku_id",skuId);
        }

        IPage<WareSkuEntity> page = this.page(
                new Query<WareSkuEntity>().getPage(params),
                queryWrapper
        );

        return new PageUtils(page);
    }

    @Override
    public void addSkuDetail(Long skuId, Long wareId, Integer skuNum) {
        QueryWrapper<WareSkuEntity> queryWrapper = new QueryWrapper<>();
        List<WareSkuEntity> wareSkuEntities = wareSkuDao.selectList(queryWrapper.eq("sku_id", skuId).eq("ware_id", wareId));
        // 根据指定wareId的存储库中是否存在指定skuId的商品，来判断是新增还是修改
        if(wareSkuEntities == null || wareSkuEntities.size() == 0){
            WareSkuEntity wareSkuEntity = new WareSkuEntity();
            wareSkuEntity.setSkuId(skuId);
            wareSkuEntity.setWareId(wareId);
            wareSkuEntity.setStock(skuNum);
            wareSkuEntity.setStockLocked(0);
            // 远程查询skuName
            /*
             有可能调用远程的info失败，或info为空，需要添加try-catch捕捉，防止因为1个skuName字段
             的获取失败，而导致整个事务回滚（这里采用try-catch的方式防止整个事务回滚）
              */
            try {
                R info = productFeignService.info(skuId);
                Map<String,Object> infoMap = (Map<String, Object>) info.get("skuInfo");
                wareSkuEntity.setSkuName((String) infoMap.get("skuName"));
            } catch (Exception e) {
                log.error(e.getMessage());
            }

            wareSkuDao.insert(wareSkuEntity);
        }else{
            wareSkuDao.addSkuDetail(skuId,wareId,skuNum);
        }


    }

    @Override
    public List<SkuStockTo> hasStock(Long[] skuIds) {
        // 查询出每个skuId对应的库存容量（需要用 stock - stock_locked，因为锁定的库存已经被其他顾客占用了）
        List<SkuStockTo> skuStockTos = new ArrayList<>();
        for (Long skuId : skuIds) {
            // 注意：库存容量stock的接收类型必须为包装类型，否则对于null型数据接收就会产生异常
            Integer stock = wareSkuDao.hasStock(skuId);
            SkuStockTo to = new SkuStockTo();
            to.setSkuId(skuId);
            // 注意：为null，直接与0比较会报错，所以需要特定的null判断与处理
            to.setHasStock(stock == null ? false : stock > 0);// 设置是否有库存的状态
            skuStockTos.add(to);
        }
        return skuStockTos;
    }

    @Override
    @Transactional(rollbackFor = Exception.class) // 开启事务功能，支持异常的回滚机制
    public Boolean lockOrder(WareLockVo wareLockVo) {
        // 当进入库存锁定业务逻辑，就会先创建1个库存锁定工作单，关联订单编号orderSn，然后保存到数据库中
        WareOrderTaskEntity wareOrderTaskEntity = new WareOrderTaskEntity();
        wareOrderTaskEntity.setOrderSn(wareLockVo.getOrderSn());
        wareOrderTaskService.save(wareOrderTaskEntity);

        // 用于MQ发送库存锁定成功后的通知信息，便于后续发生异常时回滚、解锁库存使用
        StockLockTo stockLockTo = new StockLockTo();
        stockLockTo.setTaskId(wareOrderTaskEntity.getId());

        List<OrderItemVo> items = wareLockVo.getItems();
        if(items != null && items.size() > 0){
            for (OrderItemVo item : items) {
                Long skuId = item.getSkuId();
                Integer count = item.getCount();
                // 先去查出每个skuId有能满足购买数量的wareId有哪些（TODO 按照仓库地离收货地的距离排序，找到最近的来发货）
                List<Long> wareIds = wareSkuDao.getWareIdsBySkuIdAndCount(skuId,count);
                if(wareIds == null || wareIds.size() <= 0){
                    throw new SkuWareException(skuId);// 只要抛出异常，库存就锁定失败，整体回滚
                }else{
                    // 取出第一个满足购买数量的wareId，然后去进行库存锁定
                    Long wareId = wareIds.get(0);
                    wareSkuDao.lockSkuWare(skuId,count,wareId);
                    // 若当前商品锁定成功，还会创建1个工作单详情记录，专门记录哪个wareId锁定了多少数量的skuId以及对应工作单的id
                    WareOrderTaskDetailEntity wareOrderTaskDetailEntity = new WareOrderTaskDetailEntity(null,skuId,item.getTitle(),count,wareOrderTaskEntity.getId(),wareId,1);
                    wareOrderTaskDetailService.save(wareOrderTaskDetailEntity);

                    // 构建库存锁定成功后的StockLockDetailTo信息
                    StockLockDetailTo stockLockDetailTo = new StockLockDetailTo();
                    BeanUtils.copyProperties(wareOrderTaskDetailEntity,stockLockDetailTo);
                    stockLockTo.setDetail(stockLockDetailTo);

                    // 通知MQ当前skuId对应的商品库存锁定成功
                    rabbitTemplate.convertAndSend("stock-event-exchange","stock.locked",stockLockTo);


                }
            }
        }

        return true;
    }

    @Override
    public void releaseLockStock(StockLockTo stockLockTo) {
        Long taskId = stockLockTo.getTaskId();
        // 判断是否因为库存锁定异常发生了回滚，若回滚则置flag = false
        boolean flag = true;
        StockLockDetailTo detail = stockLockTo.getDetail();
        if(detail == null){
            flag = false;
        }else{
            WareOrderTaskDetailEntity detailEntity = wareOrderTaskDetailService.getById(detail.getId());
            if(detailEntity == null){
                flag = false;
            }else{
                detail.setLockStatus(detailEntity.getLockStatus());
            }
        }
        if(flag){
            // 解锁库存
            WareOrderTaskEntity taskEntity = wareOrderTaskService.getById(taskId);

            String orderSn = taskEntity.getOrderSn();
            /*
             有个Bug：远程服务调用的请求头丢失问题，不带上session，直接被Order服务的拦截器拦截下来
             解决方案：
                1）像之前Order服务那样配置1个RequestInterceptor，重新给请求添加上老请求的header头信息，但也有些问题（未登陆时照样被拦截）
                2）在Order服务的拦截器中放行该类请求（推荐这种方法，毕竟是消息队列MQ定时性地监听到并自动解锁
                库存，不管用户是否登陆，只要满足了解锁条件，就去执行解锁逻辑）
              */
            R r = orderFeignService.getOrderEntityByOrderSn(orderSn);
            if(r != null && r.getCode() == 0){
                OrderVo orderVo = r.getData(new TypeReference<OrderVo>() {
                });
                /*
                 若订单不存在（库存锁定是成功的，但由于订单服务调用其他方法失败回滚了，订单不存在了）
                 或订单取消了（超时未支付或用户手动取消）都要根据MQ收到的解锁库存信息去进行解锁
                 TODO 有个bug：若库存回滚了，但是有一些商品之前遍历时是有库存的，导致成功设置了StockLockDetailTO信息
                 并通知MQ发送给了指定队列，过了2分钟后发给了解锁队列，判断此时orderVo为null，它会去进行解锁，但是之前
                 库存锁定时已经回滚了！
                 */
                if(orderVo == null || orderVo.getStatus() == 4){
                    // 只有是已锁定状态（1）才去进行解锁
                    if(detail.getLockStatus() == 1){
                        this.unlockStock(detail.getSkuId(),detail.getSkuNum(),detail.getWareId(),detail.getId());
                    }
                }
            }else {
                // 远程调用不稳定，则将消息拒绝后重新入队，让其他客户端继续处理消息，去实现库存解锁
                throw new RuntimeException("远程服务调用不稳定！");
            }
        }else{
            // 库存锁定失败，无需解锁
        }
    }

    @Override
    @Transactional
    // 由订单超时后主动触发的库存解锁
    public void releaseLockStock(OrderTo orderTo) {
        String orderSn = orderTo.getOrderSn();
        WareOrderTaskEntity wareOrderTaskEntity = wareOrderTaskService.getOne(new QueryWrapper<WareOrderTaskEntity>().eq("order_sn", orderSn));
        if(wareOrderTaskEntity != null){
            Long taskId = wareOrderTaskEntity.getId();
            List<WareOrderTaskDetailEntity> detailEntities = wareOrderTaskDetailService.list(new QueryWrapper<WareOrderTaskDetailEntity>().eq("task_id", taskId).eq("lock_status", 1));
            if(detailEntities != null && detailEntities.size() > 0){
                for (WareOrderTaskDetailEntity detailEntity : detailEntities) {
                    Long skuId = detailEntity.getSkuId();
                    Integer num = detailEntity.getSkuNum();
                    Long wareId = detailEntity.getWareId();
                    Long detailId = detailEntity.getId();
                    this.unlockStock(skuId,num,wareId,detailId);
                }
            }
        }
    }

    // 解锁库存（对应dao层）
    private void unlockStock(Long skuId, Integer skuNum, Long wareId,Long detailId) {
        // 更新锁定库存量
        wareSkuDao.unlockStock(skuId,skuNum,wareId);
        // 更新工作单详情表的锁定状态（由1：已锁定，改为2：已解锁）
        WareOrderTaskDetailEntity taskDetailEntity = new WareOrderTaskDetailEntity();
        taskDetailEntity.setId(detailId);
        taskDetailEntity.setLockStatus(2);
        wareOrderTaskDetailService.updateById(taskDetailEntity);
    }

    @Data
    // 封装存有指定skuId对应商品数量的仓库id
    class HasStockOfSkuId{
        private Long skuId;
        private Integer num;
        // TODO 可根据仓库地址离发货地的距离挑出最近的wareId
        private List<Long> wareId;
    }

}